
#ifndef RTCONNECTORPROXYT_H
#define RTCONNECTORPROXYT_H

#include "RtConnectorTcpT.h"
#include "RtConnectorSocksProxyT.h"
#include "RtConnectorHttpProxyT.h"
#include "RtInetAddr.h"
#include "RtTimerWrapper.h"
#include "RtHttpProxyManager.h"
#include "RtObserver.h"

template <class UpperType, class UpTrptType, class UpSockType>
class /*RT_API_EXPORT*/ CRtConnectorProxyT 
	: public IRtConnectorInternal
	, public IRtObserver
{
public:
	typedef CRtConnectorProxyT SelfType;
	typedef CRtTransportTcp TrptType;
	typedef CRtSocketStream SockType;

	CRtConnectorProxyT(IRtReactor *aReactor, UpperType &aUpper, BOOL aNeedTunnel)
		: m_pReactor(aReactor)
		, m_Upper(aUpper)
		, m_TcpConnect(aReactor, *this)
		, m_HttpConnetor(aReactor, *this)
		, m_SocksConnector(aReactor, *this)
		, m_bNeedTunnel(aNeedTunnel)
	{
		/// to start the proxy finding.
		m_pHttpProxyManager = CRtHttpProxyManager::Instance();
		RT_ASSERTE(m_pHttpProxyManager);
	}

	virtual ~CRtConnectorProxyT()
	{
		Close();
	}

	// interface IRtConnectorInternal
	virtual int Connect(const CRtInetAddr &aAddr, CRtInetAddr *aAddrLocal = NULL)
	{
		m_AddrSvr = aAddr;
		RT_ASSERTE(!aAddrLocal);

		for ( ; ; ) {
			IRtHttpProxyInfoGetter::NextReturn nrGet;
			nrGet = m_pHttpProxyManager->GetProxyInfo(
				m_AddrSvr.GetIpDisplayName(),
				m_AddrSvr.GetPort(),
				m_pProxyInfo.ParaOut());
			if (nrGet == IRtHttpProxyInfoGetter::NEXT_SUCCESS) {
				if (!m_pProxyInfo) {
					// As a proxy connector, we don't support "DIRECT".
					RT_WARNING_TRACE_THIS("CRtConnectorProxyT::Connect, as a proxy connector, we don't support \"DIRECT\"!");
					//continue;
					return -1;
				}

				if (!m_bNeedTunnel) {
					CRtInetAddr addrTcp(
						m_pProxyInfo->GetHostName().c_str(), 
						m_pProxyInfo->GetPort());
					if (m_TcpConnect.Connect(addrTcp) == 0)
						return 0;
					m_TcpConnect.Close();
				}
				else if (m_pProxyInfo->GetProxyType() <= CRtHttpProxyInfo::HTTPS_PROXY) {
					// for HTTP proxy
					m_HttpConnetor.SetProxyInfo(m_pProxyInfo.ParaIn());
					if (m_HttpConnetor.Connect(m_AddrSvr, aAddrLocal) == 0)
						return 0;
					m_HttpConnetor.Close();
				}
				else {
					/// for SOCKS proxy
					m_SocksConnector.SetProxyInfo(m_pProxyInfo.ParaIn());
					if (m_SocksConnector.Connect(m_AddrSvr, aAddrLocal) == 0)
						return 0;
					m_SocksConnector.Close();
				}
			}
			else if (nrGet == IRtHttpProxyInfoGetter::NEXT_WOULDBLCOK) {
				m_pHttpProxyManager->AddObserver(this);
				return 0;
			}
			else {
				RT_WARNING_TRACE_THIS("CRtConnectorProxyT::Connect,"
					" GetProxyInfo() none."
					" addr=" << m_AddrSvr.GetIpDisplayName() << 
					" port=" << m_AddrSvr.GetPort());
				return -1;
			}
		}

		RT_ASSERTE(!"CRtConnectorProxyT::Connect, can't reach here!");
		return -1;
	}
	
	virtual int Close()
	{
		m_pProxyInfo = NULL;
		m_TcpConnect.Close();
		m_HttpConnetor.Close();
		m_SocksConnector.Close();
		m_pHttpProxyManager->RemoveObserver(this);
		return 0;
	}
	
	// we have use IRtTransport instead TrptType due to ConnectorHttpProxy
	int OnConnectIndication(
		RtResult aReason, 
		IRtTransport *aTrpt,
		IRtConnectorInternal *aId)
	{
		CRtAutoPtr<IRtTransport> pAutoDelete(aTrpt);
		if (RT_SUCCEEDED(aReason)) {
			RT_ASSERTE(aTrpt);
			BOOL bAlive = FALSE;
			aTrpt->GetOption(RT_OPT_TRANSPORT_SOCK_ALIVE, &bAlive);
			if (!bAlive) {
				RT_ERROR_TRACE_THIS("CRtConnectorProxyT::OnConnectIndication,"
					" transport isn't alive! err=" << errno);
				goto fail;
			}

			RT_HANDLE hdNew = RT_INVALID_HANDLE;
			aTrpt->GetOption(RT_OPT_TRANSPORT_FD, &hdNew);
			RT_ASSERTE(hdNew != RT_INVALID_HANDLE);

			RT_HANDLE hdNULL = RT_INVALID_HANDLE;
			RtResult rv = aTrpt->SetOption(RT_OPT_TRANSPORT_FD, &hdNULL);
			RT_ASSERTE(RT_SUCCEEDED(rv));

			UpTrptType *pTrans = new UpTrptType(m_pReactor);
			pTrans->GetPeer().SetHandle(hdNew);
			pTrans->SetOption(RT_OPT_TRANSPORT_PEER_ADDR, &m_AddrSvr);
			m_Upper.OnConnectIndication(RT_OK, pTrans, this);
			return 0;
		}

fail:
		Close();
		if (RT_SUCCEEDED(aReason))
			aReason = RT_ERROR_NETWORK_SOCKET_ERROR;
		else if (aReason != RT_ERROR_NETWORK_PROXY_SERVER_UNAVAILABLE) {
			m_pHttpProxyManager->ClearCacheProxy(
				m_AddrSvr.GetIpDisplayName(), 
				m_AddrSvr.GetPort());
		}
		m_Upper.OnConnectIndication(aReason, NULL, this);
		return 0;
	}

	virtual void OnObserve(LPCSTR aTopic, LPVOID aData = NULL)
	{
		RT_ASSERTE(!strcmp(aTopic, "HttpProxyManager"));
		if (Connect(m_AddrSvr) == -1) {
			Close();
			m_Upper.OnConnectIndication(
				RT_ERROR_NETWORK_UNKNOWN_ERROR,
				NULL, 
				this);
		}
	}
	
private:
	IRtReactor *m_pReactor;
	UpperType &m_Upper;
	CRtConnectorTcpT<SelfType, TrptType, SockType> m_TcpConnect;
	CRtConnectorHttpProxyT<SelfType, TrptType, SockType> m_HttpConnetor;
	CRtConnectorSocksProxyT<SelfType, TrptType, SockType> m_SocksConnector;
	CRtAutoPtr<CRtHttpProxyInfo> m_pProxyInfo;
	CRtInetAddr m_AddrSvr;
	CRtHttpProxyManager *m_pHttpProxyManager;
	BOOL m_bNeedTunnel;
};

#endif // !RTCONNECTORPROXYT_H
